/*
This software is distributed under the Simplified BSD License:

Copyright (c) 2008, Chris Venter <chris.venter@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, 
    	this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, 
    	this list of conditions and the following disclaimer in the documentation 
    	and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE.
*/
#ifndef GENERIK_SOCKET_H
#define GENERIK_SOCKET_H

/** \file socket.h
General socket handling: This file defines two classes of sockets, one
meant for server duty (listening for connections) and the other for managing
actual socket connections and transferring data.
*/

#ifdef USING_MINGW32

#include <winsock2.h>
#include <windows.h>
#include <winbase.h>
#include <wincon.h>
#include <mswsock.h>
#include <ws2tcpip.h>

#include "mingw32/poll.h"
#include "mingw32/wsautils.h"

typedef int socklen_t;
#define SETSOCKETOPT_TYPE char
#define SEND_TYPE char
#define RECEIVE_TYPE

#else // if not USING_MINGW32

// types
#include <sys/types.h>
// gethostbyname
#include <netdb.h>
// socket
#include <sys/socket.h>
// hton*
#include <netinet/in.h>
// inet_addr, inet_aton
#include <arpa/inet.h>
// poll
#include <poll.h>

#define SETSOCKETOPT_TYPE int
#define SEND_TYPE int
#define RECEIVE_TYPE (void*)

#endif // USING_MINGW32

// strerror
#include <string.h>
// errno
#include <errno.h>
// ???
#include <unistd.h>

#include <iostream>
#include <stdexcept>

#include "tracing.h"

namespace generik
{

	#define TRACE_POLL_EVENT(X) if (event & X) bits += #X" ";
	std::string trace_poll_event(short event)
	{
		std::string bits = "";
		TRACE_POLL_EVENT(POLLERR);
		TRACE_POLL_EVENT(POLLHUP);
		TRACE_POLL_EVENT(POLLIN);
		TRACE_POLL_EVENT(POLLNVAL);
		TRACE_POLL_EVENT(POLLOUT);
		TRACE_POLL_EVENT(POLLPRI);
#ifndef USING_MINGW32
		TRACE_POLL_EVENT(POLLRDBAND);
		TRACE_POLL_EVENT(POLLRDNORM);
		TRACE_POLL_EVENT(POLLWRBAND);
		TRACE_POLL_EVENT(POLLWRNORM);
#endif
		return bits;
	}
		
	bool poll(int fd = STDIN_FILENO, short events = POLLIN, int msec = -1)
	{
		REM_GENERIK_TRACER;
#ifdef _PTHREAD_H
		int _prev_state;
		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &_prev_state);
#endif
		pollfd pfd;
		pfd.fd = fd;
		pfd.events = events;
		int result = ::poll(&pfd, 1, msec);
		GENERIK_TRACE_VALUE(result);
		bool ret = false;
		if (result > 0)
		{
			GENERIK_TRACE_VALUE(trace_poll_event(events));
			GENERIK_TRACE_VALUE(trace_poll_event(pfd.revents));
			if (pfd.revents == events)
			{
				ret = true;
			}
		}
#ifdef _PTHREAD_H
		pthread_setcancelstate(_prev_state, 0);
#endif
		GENERIK_TRACE_VALUE(ret);
		return ret;
	}
	
	namespace socket
	{

		/// localhost address. this is pretty universal.
		const char* LOCALHOST = "127.0.0.1";

		/** using BLOCK in calls which expect a millisecond argument results
		in blocking behaviour, ie. will NOT timeout.
		*/
		const int BLOCK 	= -1;

		/** using NONBLOCK in calls which expect a millisecond argument results
		in nonblocking behaviour, ie. will immediately return.
		*/
		const int NONBLOCK 	= 0;

		/// the base socket class
		/** This is NOT an interface class, but simply a toolkit for the
		socket implementation classes to re- and ab-use
		*/
		struct socket_base
		{
			/// stores the socket's info
			sockaddr_in _addr;
			/// stores the socket's id
			int _socketid;

			void init()
			{
				REM_GENERIK_TRACER;
#ifdef USING_MINGW32
				WSAInit();
#endif
			}
			/// creates a socket_base
			socket_base()
			{
				REM_GENERIK_TRACER;
				init();
			}

			/// creates a socket_base
			/** Uses the specified socketid and hostinfo
			*/
			socket_base(int socketid_in, const sockaddr_in& addr_in)
			: _addr(addr_in)
			, _socketid(socketid_in)
			{
				REM_GENERIK_TRACER;
				init();
			}

			/// closes the connection
			virtual ~socket_base()
			{
				REM_GENERIK_TRACER;
				GENERIK_TRACE_VALUE((void*)this);
				close();
			}
			
			/// resolves a given dotted-notation network address.
			/** Takes the dotted addres and resolves it into its numerical
			representation. This is NOT a DNS lookup, simply a conversion...
			*/
			void resolve_address(in_addr& in_addr_in, const std::string& address_in = LOCALHOST)
			{
				GENERIK_TRACER;
#ifdef USING_MINGW32
				unsigned long address = INADDR_NONE;
				address = inet_addr(address_in.c_str());
				GENERIK_TRACE_VALUE(address);
				if (address == INADDR_NONE)
				{
					printf("inet_addr failed and returned INADDR_NONE\n");
					WSACleanup();
				}   
				if (address == INADDR_ANY) {
					printf("inet_addr failed and returned INADDR_ANY\n");
					WSACleanup();
				}
				in_addr_in = *((in_addr*)&address);
#else
				int error = inet_pton(AF_INET, address_in.c_str(), &(in_addr_in));
				if (!error)
				{
					std::string msg = "error during inet_pton: ";
					throw std::runtime_error(msg + strerror(errno));
				}
#endif
			}
			
			/// does the reverse of resolve_address
			/** Accepts the given numerical address and returns the dotted string notation
			*/
			std::string reverse_address(const in_addr& in_addr_in) const
			{
#ifndef USING_MINGW32
				char str[INET_ADDRSTRLEN];
				const char* result = inet_ntop(AF_INET, &(in_addr_in), str, INET_ADDRSTRLEN);

				//int error = inet_ntop(address_in.c_str(), &(in_addr_in));
				if (result != str)
				{
					std::string msg = "error during inet_ntop: ";
					throw std::runtime_error(msg + strerror(errno));
				}
				return str;
#else
				return "";
#endif
			}
			
			/// initialises a sockaddr_in struct.
			/** The sockaddr_in c-struct does not have a constructor, so use
			this to initialise it. Has some sane default values...
			*/
			void init_sockaddr(
				unsigned short int port_in = 0,
				const char* address_in = LOCALHOST,
				short int family_in = AF_INET
				)
			{
				GENERIK_TRACER;
				_addr.sin_family = family_in;
				_addr.sin_port = htons(port_in);
				resolve_address(_addr.sin_addr, address_in);
				//
				memset(&(_addr.sin_zero), '\0', 8);
			}

			/// sets the options of a socket
			/** Specifically, sets the socket to ignore a stale user of a port,
			and to try and re-use the port.
			Otherwise, the bind() call might raise an "Address already in use" error.
			*/
			int setsocketopt()
			{
				GENERIK_TRACER;
				SETSOCKETOPT_TYPE yes = 1;
				int error = ::setsockopt(_socketid, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
				if (error < 0)
				{
					std::string msg = "error during setsocketopt: ";
#ifdef USING_MINGW32
					msg += WSAGetLastErrorMessage("");
#else
					msg += strerror(errno);
#endif
					throw std::runtime_error(msg);
				}
				return error;
			}

			/// binds the socket to a specified port
			/** Uses the sockaddr info to determine the host and port
			*/
			int bind()
			{
				int error = ::bind(_socketid, (struct sockaddr *)&_addr, sizeof(struct sockaddr));
				if (error < 0)
				{
					std::string msg = "error during bind: ";
#ifdef USING_MINGW32
					msg += WSAGetLastErrorMessage("");
#else
					msg += strerror(errno);
#endif
					throw std::runtime_error(msg);
				}
				return error;
			}

			/// sets the socket to listen to its bound port
			/** Sets the number of queued connections.
			Use bind() before this!
			*/
			void listen(int backlog = 20)
			{
				int error = ::listen(_socketid, backlog);
				if (error < 0)
				{
					std::string msg = "error during listen: ";
					throw std::runtime_error(msg + strerror(errno));
				}
			}

			/// connects the socket to a specified host:port
			/** Obviously, there should be some accepting form of socket waiting at the port,
			otherwise a "Connection refused" error is raised
			*/
			bool connect()
			{
				GENERIK_TRACER;
				int error = ::connect(_socketid, (struct sockaddr *)&_addr, sizeof(struct sockaddr));
				if (error < 0)
				{
					GENERIK_TRACE("connect failed");
					return false;
				}
				GENERIK_TRACE("connect succeeded");
				return true;
			}

			/// connects the socket to a specified host:port
			/** Obviously, there should be some accepting form of socket waiting at the port,
			otherwise a "Connection refused" error is raised
			*/
			bool connect(int sleep_for, int tries)
			{
				REM_GENERIK_TRACER;
				int count = 0;
				int error = ::connect(_socketid, (struct sockaddr *)&_addr, sizeof(struct sockaddr));
				while (error < 0 && (count < tries))
				{
					GENERIK_TRACE("connect failed");
					usleep(sleep_for*1024);
					error = ::connect(_socketid, (struct sockaddr *)&_addr, sizeof(struct sockaddr));
				}
				GENERIK_TRACE("connect succeeded");
				return true;
			}
			
			void close()
			{
				GENERIK_TRACER;
				GENERIK_TRACE_VALUE((void*)this);
				::close(_socketid);
			}

			///  initialises a socket, and returns its handle
			void socket()
			{
				_socketid = ::socket(_addr.sin_family, SOCK_STREAM, 0);
			}

			// this unfortunately needs to go
			bool poll(short events, int msec)
			{
				REM_GENERIK_TRACER;
				bool result = generik::poll(_socketid, events, msec);
				GENERIK_TRACE_VALUE(result);
				return result;
			}

			int poll(short events_in, short& events_out, int msec = -1)
			{
				REM_GENERIK_TRACER;
				pollfd pfd;
				pfd.fd = _socketid;
				pfd.events = events_in;
				pfd.revents = 0;
				int result = ::poll(&pfd, 1, msec);
				events_out = pfd.revents;
				return result;
			}
			
			bool is_open()
			{
				short events = POLLIN;
				short revents = 0;
				if (poll(events, revents, 0))
				{
					// an error has occurred. Do we throw something? or just return false?
					return false;
				}
				if (revents && (POLLERR || POLLHUP || POLLNVAL))
				{
					return false;
				}
				return true;
			}
		
			std::string get_ip() const
			{
				return socket_base::reverse_address(_addr.sin_addr);
			}
			
			/** returns the host name from the connection.
			\todo better (ie. actual) error handling...
			*/
			std::string get_hostname() const
			{
#ifndef USING_WINDOWS
/* MINGW32 and Cygwin do not (at the moment) know of getnameinfo... */
				char host[1024];
				char service[20];
				int result = ::getnameinfo((sockaddr*)&_addr, sizeof(sockaddr), host, sizeof(host), service, sizeof(service), 0);
				if (result == -1)
				{
					// hammer time!
				}
				return host;
#else
				return "unknown";
#endif
			}
			
		};

		/// a socket connection
		/** This writes/reads data on a socket
		\todo Create an output operator <<, so that we can write a connection's details easily.
		For example this could be something like:
		\li connection type/family
		\li ip address, network and printable
		\li port number
		*/
		struct connection
		: public socket_base
		{
			connection()
			//: _buffer(this)
			{}
			
			/// constructs a socket
			/** Creates a socket and connects it to the specified port on the
			specified host. Usually used from the client-side, to connect to some
			server on a specified port
			\todo better error handling...
			*/
			connection(int port_in, const std::string& addr_in = LOCALHOST)
			{
				if (!connect(port_in, addr_in))
				{
					// todo: an error occurred!
				}
			}

			bool connect(int port_in, const std::string& addr_in = LOCALHOST)
			{
				GENERIK_TRACER;
				init_sockaddr(port_in, addr_in.c_str());
				socket();
				setsocketopt();
				return socket_base::connect();
			}

			bool connect(int wait_for, int tries, int port_in, const std::string& addr_in = LOCALHOST)
			{
				init_sockaddr(port_in, addr_in.c_str());
				socket();
				setsocketopt();
				return socket_base::connect(wait_for, tries);
			}

			/// constructs a socket
			/** Assigns the specified socket id and connection info to the socket.
			This is usually called by a listening socketlistener when it
			receives a connection, ie. on the server-side of things
			*/
			connection(int socketid_in, const sockaddr_in& addr_in)
			: socket_base(socketid_in, addr_in)
			//, _buffer(this)
			{}

			/// closes the connection
			~connection()
			{}

			/// sends data over a connection
			/** this is just an interface to the stock send() function.
			Any buffer management should be done above this
			*/
			int send_poll(const char* data_in, int length_in, int flags_in = 0, int msec_in = 0)
			{
				if (poll(POLLOUT, msec_in) > 0)
				{
					return ::send(_socketid, data_in, length_in, flags_in);
				}
				else
				{
					return 0;
				}
			}

			/// sends string data over a connection
			/** this is just an interface to the stock send() function.
			Any buffer management should be done above this
			*/
			int send_poll(const std::string& string_in, int flags_in = 0, int msec_in = 0)
			{
				return send_poll(string_in.c_str(), string_in.length(), flags_in, msec_in);
			}

			/**
			\todo needs to send UNTIL the entire string has been sent! 
			*/
			int send(const std::string& string_in, int flags_in = 0)
			{
				// needs to send UNTIL the entire string has been sent! 
				// question: should it send the terminating character, if any? 
				// yes???
				return send(string_in.c_str(), string_in.length(), flags_in);
			}
			
			int send(const char* data_in, int length, int flags_in = 0)
			{
				REM_GENERIK_TRACER;
				GENERIK_TRACE_VALUE(length);
				int sent = ::send(_socketid, data_in, length, flags_in);
				GENERIK_TRACE_VALUE(sent);
				return sent;
			}

			int send(SEND_TYPE ch, int flags_in = 0)
			{
				REM_GENERIK_TRACER;
				int sent = 0;
				GENERIK_TRACE_VALUE((unsigned char)ch);
				sent = ::send(_socketid, &ch, 1, flags_in);
				return sent;
			}

			/// retrieves any data from the connection
			/** It reads from the connection, and returns the
			data in a string.
			\todo test it with binary data, ie. 0's...
			*/
			int recv_poll(char* data_in, int length_in, unsigned int flags_in = 0, int msec_in = 0)
			{
				std::cout<<"polling..."<<std::endl;
				if (poll(POLLIN, msec_in) > 0)
				{
					return ::recv(_socketid, RECEIVE_TYPE data_in, length_in, flags_in);
				}
				else
				{
					return 0;
				}
			}

			int recv(char* data_in, int length_in, unsigned int flags_in = 0)
			{
				REM_GENERIK_TRACER;
				int received = ::recv(_socketid, RECEIVE_TYPE data_in, length_in, flags_in);
				GENERIK_TRACE_VALUE(received);
				GENERIK_TRACE_VALUE(data_in[received-1]);
				return received;
			}
			
			int recv(int* ch, unsigned int flags_in = 0)
			{
				REM_GENERIK_TRACER;
				int count = 0;
				char ch_;
				count = ::recv(_socketid, RECEIVE_TYPE &ch_, 1, flags_in);
				*ch = ch_;
				GENERIK_TRACE_VALUE(count);
				GENERIK_TRACE("recv: ["<<(unsigned char)(*ch)<<"]: ["<<*ch<<"]");
				return count;
			}

			/**
			\todo implement this!
			*/
			std::string recv(char delim = '\n', unsigned flags_in = 0)
			{
				// needs to read until a deliminator character is found.
				// question: add the deliminator to the string? yes?
				// so empty string means EOF?
				return "TODO!!!";
			}
		};

		/// socket server
		/** This listens for connections on a specified port, and produces
		a socketconnection each time a connection is made
		*/
		struct server
		: public socket_base
		{

			server()
			{
				GENERIK_TRACER;
			}
			
			/// creates a socketlistener on the specified port
			server(int port_in, const std::string& hostname_in = LOCALHOST, int backlog = 20)
			{
				open(port_in, hostname_in, backlog);
			}

			~server()
			{
				close();
			}
			
			void open(int port_in, const std::string& hostname_in = LOCALHOST, int backlog = 20)
			{
				GENERIK_TRACER;
				init_sockaddr(port_in, hostname_in.c_str());
				socket();
				setsocketopt();
				bind();
				listen(backlog);
			}
			
			/// accepts a connection
			/** If a new connection is found, a new socketconnection
			is created on the heap, and initialised with the new connection, and
			returned.
			*/
			connection* accept(int msec_in = -1)
			{
				GENERIK_TRACER;
				connection* newconnection = 0;
				int pollresult = poll(POLLIN, msec_in);
				GENERIK_TRACE_VALUE(pollresult);
				if (pollresult > 0)
				{
					GENERIK_TRACE("new connection found");
					sockaddr_in clientaddr;
					socklen_t addrlen = sizeof(sockaddr_in);
					int newsocket_id = ::accept(_socketid, (sockaddr*)&clientaddr, &addrlen);
					if (newsocket_id < 0)
					{
						std::string msg = "error during accept: ";
						throw std::runtime_error(msg + strerror(errno));
					}
					newconnection = new connection(newsocket_id, clientaddr);
				}
				else
				{
					GENERIK_TRACE("no connection found");
				}
				return newconnection;
			}

		};

	}

};

#include <ostream>

std::ostream& operator<<(std::ostream& stream, const generik::socket::socket_base& socket)
{
	return stream<<socket.get_ip()<<" "<<socket.get_hostname();
	//"new client connection, from "<<c->get_ip()<<" ["<<c->get_hostname()<<"]");
}


#endif

